package cn.thoughtworks.school.services;

import cn.thoughtworks.school.entities.*;
import cn.thoughtworks.school.entities.dto.AssignmentQuizData;
import cn.thoughtworks.school.handlers.BusinessException;
import cn.thoughtworks.school.repositories.BasicQuizChoicesRepository;
import cn.thoughtworks.school.repositories.BasicQuizRepository;
import cn.thoughtworks.school.repositories.BasicQuizSubmissionRepository;
import cn.thoughtworks.school.requestParams.YmlConvertQuiz;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.persistence.criteria.Predicate;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.stream.Collectors.groupingBy;

@Service
public class BasicQuizService {

    @Autowired
    private BasicQuizRepository basicQuizRepository;
    @Autowired
    private BasicQuizExcelImportService basicQuizExcelImportService;
    @Autowired
    private UserCenterService userCenterService;
    @Autowired
    private Utils utils;
    @Autowired
    private QuizService quizService;
    @Autowired
    private TagService tagService;
    @Autowired
    private BasicQuizChoicesRepository basicQuizChoicesRepository;
    @Autowired
    private BasicQuizSubmissionRepository basicQuizSubmissionRepository;

    public void excelImport(MultipartFile file, User current) throws IOException, BusinessException {
        basicQuizExcelImportService.init(file);
        basicQuizExcelImportService.importExcel(current);
    }

    public Page getBasicQuizzesPageable(String quizType, Pageable pageable, Long id, Long creator, String description, List<String> tags, User user) {
        Long currentOrganizationId = userCenterService.getUserCurrentOrganizationId(user.getId());
        List<Long> quizIds = getQuizIdsByTags(tags);
        Page<BasicQuiz> basicQuizPage = getBasicQuizPage(quizType, currentOrganizationId, pageable, id, creator, description, quizIds);
        return formatBasicQuizPage(pageable, basicQuizPage);
    }

    private Page formatBasicQuizPage(Pageable pageable, Page<BasicQuiz> basicQuizPage) {
        List<BasicQuiz> basicQuizzes = new ArrayList<>(basicQuizPage.getContent());
        String userIdStr = utils.getMakerIds(basicQuizzes);
        List<Map> users = userCenterService.getUsersByIds(userIdStr);
        List<Map> quizzes = basicQuizzes.stream()
            .sorted(Comparator.comparing(BasicQuiz::getId).reversed())
            .map(basicQuiz -> formatBasicQuizByUsers(basicQuiz, users))
            .collect(Collectors.toList());
        return new PageImpl(quizzes, pageable, basicQuizPage.getTotalElements());
    }

    private Page<BasicQuiz> getBasicQuizPage(String quizType, Long organizationId, Pageable pageable, Long id, Long userId, String description, List<Long> quizIds) {
        return basicQuizRepository.findAll((Specification<BasicQuiz>) (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = quizService.getDefaultPredicates(organizationId, id, userId, description, quizIds, root, criteriaBuilder);
            if (Objects.nonNull(quizType)) {
                predicates.add(criteriaBuilder.equal(root.get("type").as(String.class), quizType));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        }, pageable);
    }

    private List<Long> getQuizIdsByTags(List<String> tags) {
        List<BasicQuiz> quizzes = basicQuizRepository.findBasicQuizzesByTags(tags);
        if (Objects.nonNull(tags) && quizzes.isEmpty()) {
            return Arrays.asList(-1L);
        }
        return quizzes.stream().map(BasicQuiz::getId).collect(Collectors.toList());
    }

    public Map formatBasicQuizByUsers(BasicQuiz basicQuiz, List<Map> users) {
        ObjectMapper oMapper = new ObjectMapper();

        Map data = oMapper.convertValue(basicQuiz, Map.class);
        String userName = "";
        if (data.get("makerId") != null) {
            userName = utils.getQuizMaker(users, data);
        }
        data.put("maker", userName);
        List<BasicQuizChoices> choices = basicQuiz.getChoices();
        data.put("choices", formatChoices(choices));

        if ("BASIC_BLANK_QUIZ".equals(basicQuiz.getType())) {
            data.remove("choices");
        } else if ("MULTIPLE_CHOICE".equals(basicQuiz.getType())) {
            String[] answers = basicQuiz.getAnswer().split(",");

            data.put("answer", answers);
        }

        return data;
    }

    private List formatChoices(List<BasicQuizChoices> choices) {
        List result = new ArrayList();
        for (BasicQuizChoices choice : choices) {
            result.add(choice.getChoice());
        }

        return result;
    }

    protected void convertUpdateBasicQuizzes(YmlConvertQuiz converter, User current) {
        Long organizationId = userCenterService.getUserCurrentOrganizationId(current.getId());
        List<Tag> tags = tagService.updateTags(converter, organizationId, current.getId());

        converter.filterBasicQuizzes().forEach(quiz -> {
            List<String> tagsName = (List<String>) quiz.get("tags");
            List<Tag> quizTags = tagsName.stream().map(name -> tags.stream()
                .filter(tag -> tag.getName().equals(name)).findFirst().get())
                .collect(Collectors.toList());

            BasicQuiz basicQuiz = BasicQuiz.buildBasicQuiz(quiz, current.getId(), organizationId, quizTags);
            saveBasicQuiz(basicQuiz);

            List options = (List) quiz.get("options");
            saveBasicChoices(basicQuiz, options);
        });
    }

    private void saveBasicQuiz(BasicQuiz basicQuiz) {
        if (Objects.isNull(basicQuiz.getId())) {
            basicQuizRepository.save(basicQuiz);
        } else {
            BasicQuiz foundBasicQuiz = basicQuizRepository.findById(basicQuiz.getId()).get();
            foundBasicQuiz.updateSingleChoiceQuiz(basicQuiz);
            basicQuizRepository.save(foundBasicQuiz);
        }
    }

    private void saveBasicChoices(BasicQuiz basicQuiz, List options) {
        List<BasicQuizChoices> basicQuizChoices = new ArrayList<>();
        for (int index = 0; index < options.size(); ++index) {
            BasicQuizChoicesComplexPK complexPk = new BasicQuizChoicesComplexPK(index, basicQuiz.getId());
            basicQuizChoices.add(new BasicQuizChoices(complexPk, options.get(index).toString()));
        }
        basicQuizChoicesRepository.saveAll(basicQuizChoices);
    }

    public List<AssignmentQuizData> copy(List<AssignmentQuizData> basicQuizzesData) {
        List<Long> ids = basicQuizzesData.stream().map(AssignmentQuizData::getQuizId).collect(Collectors.toList());
        List<BasicQuiz> basicQuizzes = basicQuizRepository.findByIdIn(ids);

        basicQuizzesData.forEach(quizData -> {
            Long creatorId = quizData.getCreatorId();
            BasicQuiz basicQuiz = basicQuizzes.stream()
                .filter(quiz -> quiz.getId().equals(quizData.getQuizId()))
                .findFirst().get();
            basicQuiz.setTags(quizService.copyTags(quizData.getOrganizationId(), basicQuiz.getTags(), creatorId));
            List<BasicQuizChoices> basicQuizChoices = basicQuiz.getChoices();
            basicQuiz = BasicQuiz.copy(basicQuiz, creatorId, quizData.getOrganizationId());
            basicQuizRepository.save(basicQuiz);
            if (!"BASIC_BLANK_QUIZ".equals(basicQuiz.getType())) {
                List<String> choices = basicQuizChoices.stream().map(BasicQuizChoices::getChoice)
                    .collect(Collectors.toList());
                basicQuizChoices = genBasicQuizChoices(choices, basicQuiz.getId());
                basicQuizChoicesRepository.saveAll(basicQuizChoices);
            }
            quizData.setNewQuizId(basicQuiz.getId());
        });
        return basicQuizzesData;
    }

    public List<BasicQuizChoices> genBasicQuizChoices(List<String> choices, Long quizId) {
        List<BasicQuizChoices> result = new ArrayList<>();

        int i = 0;
        for (String choice : choices) {
            if (Objects.equals("", choice.trim())) {
                continue;
            }
            BasicQuizChoicesComplexPK complexPk = new BasicQuizChoicesComplexPK(i, quizId);
            BasicQuizChoices basicQuizChoice = new BasicQuizChoices(complexPk, choice);
            result.add(basicQuizChoice);
            i++;
        }
        return result;
    }

    public Map<Long, List<BasicQuizSubmission>> calculateAssignmentScore(Long assignmentId) {
        List<BasicQuizSubmission> basicQuizSubmissions = basicQuizSubmissionRepository.findByAssignmentId(assignmentId);
        List<BasicQuiz> basicQuizzes = basicQuizRepository.findByIdIn(basicQuizSubmissions.stream().map(BasicQuizSubmission::getQuizId).collect(Collectors.toList()));
        updateSubmissions(basicQuizSubmissions,basicQuizzes);

        return basicQuizSubmissions.stream().collect(groupingBy(BasicQuizSubmission::getUserId));

    }

    private void updateSubmissions(List<BasicQuizSubmission> basicQuizSubmissions, List<BasicQuiz> basicQuizzes) {
        basicQuizSubmissions.forEach(basicQuizSubmission -> {
            BasicQuiz current = basicQuizzes.stream()
                .filter(quiz -> quiz.getId().equals(basicQuizSubmission.getQuizId()))
                .findFirst().get();
            basicQuizSubmission.updateResult(isAnswerCorrect(current, basicQuizSubmission.getUserAnswer()));
        });
        basicQuizSubmissionRepository.saveAll(basicQuizSubmissions);
    }

    public Boolean isAnswerCorrect(BasicQuiz basicQuiz, String userAnswer) {
        if (basicQuiz.getType().equals("MULTIPLE_CHOICE")) {
            return Stream.of(basicQuiz.getAnswer().split(",")).sorted().collect(Collectors.joining(","))
                .equals(
                    Stream.of(userAnswer.split(",")).sorted().collect(Collectors.joining(","))
                );
        }

        return basicQuiz.getAnswer().equals(userAnswer);
    }
}
